/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.client.java;

import java.io.InputStream;
import java.io.Reader;

import net.interknit.knitter.client.layout.MethodEl;

public class JavaMethodSourceWriter extends JavaTypeSourceWriter
{
	protected String invokerName()
	{
		String invokerName = ((MethodEl) getTypeEl()).getName();

		for(String inParameterType : ((MethodEl) getTypeEl()).getInParameterTypes())
		{
			inParameterType = JavaSourceWriterUtils.toJavaType(inParameterType);
			invokerName += "_" + inParameterType.replace('.', '_');
		}

		return invokerName;
	}

	protected String source()
	{
		MethodEl methodEl = (MethodEl) getTypeEl();

		StringBuilder sb = new StringBuilder();

		String invokerName = invokerName();

		sb.append(JavaSourceWriterUtils.fieldDeclarationSource(invokerName, "WebClient"));
		sb.append("\n");

		String returnType = "void";
		if(! methodEl.getOutParameterTypes().isEmpty())
			returnType = JavaSourceWriterUtils.toJavaType(methodEl.getOutParameterTypes().get(0));

		String returnTypeClass = (returnType.equals("void") ? "" : (returnType + ".class"));

		sb.append("\t" + "public " + returnType + " " + methodEl.getName() + "(");

		for(int i = 0; i < methodEl.getInParameterTypes().size(); i++)
		{
			if(i > 0) sb.append(", ");

			String paramType = JavaSourceWriterUtils.toJavaType(methodEl.getInParameterTypes().get(i));
			sb.append(paramType + " " + "arg" + (i + 1));
		}

		int recordMarkerArgIndex = -1;

		if(methodEl.isRecordMarkerUsed())
		{
			if(methodEl.getInParameterTypes().size() > 0) sb.append(", ");
			recordMarkerArgIndex = methodEl.getInParameterTypes().size() + 1;
			sb.append("java.lang.Object" + " " + "arg" + recordMarkerArgIndex);
		}

		sb.append(")").append("\n");

		sb.append("\t" + "{").append("\n");
		sb.append("\t" + "\t" + invokerName + " = " + invokerName + ".back(true);").append("\n");

		//
		// special case: if the return type of the method is any one of the following,
		// the accept("*/*") header is required for the communication to work.  without
		// this a 406 error is returned by the server.
		//   - java.ioa.InputStream or it subclasses ("byte streams")
		//   - java.io.Reader or its subclasses ("character streams")
		//   - java.io.File or its subclasses ("raw file")
		//
		if(
				returnType.equals(InputStream.class.getName()) ||
				returnType.equals(Reader.class.getName()))
		{
			sb.append("\t" + "\t" + invokerName + ".accept(\"*/*\");").append("\n");
		}
		// end special case.

		String type = methodEl.getType();

		if(type != null && (type.equals("GET") || type.equals("DELETE")))
		{
			if(methodEl.isRecordMarkerUsed() && recordMarkerArgIndex > -1)
			{
				sb.append("\t" + "\t" + invokerName + " = " + invokerName);
				sb.append(".path(str(" + "arg" + recordMarkerArgIndex + "))").append(";").append("\n");
			}

			// reset the query parameters so that they do not get appended
			// multiple times across multiple invocations of this method.
			sb.append("\t" + "\t" + invokerName + ".resetQuery();").append("\n");

			if(! methodEl.getInParameterTypes().isEmpty())
				sb.append("\t" + "\t" + invokerName + " = " + invokerName);

			for(int i = 0; i < methodEl.getInParameterTypes().size(); i++)
			{
				if(methodEl.isQueryParamsUsed())
				{
					String paramName = methodEl.getInParameterNames().get(i);

					sb.append(".query(\"" + paramName + "\", str(" + "arg" + (i + 1) + "))");
				}
				else
				{
					sb.append(".path(str(" + "arg" + (i + 1) + "))");
				}
			}

			if(! methodEl.getInParameterTypes().isEmpty())
				sb.append(";").append("\n");

			sb.append("\t" + "\t" + "setAdditionalInfo(" + invokerName + ");").append("\n");
			sb.append("\t" + "\t" + "logInvocation(" + invokerName + ");").append("\n");

			sb.append("\t" + "\t");
			if(type.equals("GET"))
			{
				if(methodEl.getOutParameterTypes().isEmpty())
				{
					sb.append("checkFault(");
				}
				else
				{
					sb.append("return ");
				}
				sb.append(invokerName + ".get(" + returnTypeClass + ")");

				if(methodEl.getOutParameterTypes().isEmpty())
				{
					sb.append(")");
				}

				sb.append(";").append("\n");
			}
			else if(type.equals("DELETE"))
			{
				if(returnTypeClass != null && ! returnTypeClass.isEmpty())
				{
					sb.append("return ");
					sb.append(invokerName + ".invoke(");
					sb.append("\"" + type + "\"").append(", ");
					sb.append("null, ");
					sb.append(returnTypeClass);
					sb.append(");").append("\n");
				}
				else
				{
					sb.append(invokerName + ".delete();").append("\n");

					if(! methodEl.getOutParameterTypes().isEmpty())
						sb.append("\t" + "\t" + "return null;").append("\n");
				}
			}
		}
		else if(type != null && (type.equals("PUT") || type.equals("POST")))
		{
			if(methodEl.isRecordMarkerUsed() && recordMarkerArgIndex > -1)
			{
				sb.append("\t" + "\t" + invokerName + " = " + invokerName);
				sb.append(".path(str(" + "arg" + recordMarkerArgIndex + "))").append(";").append("\n");
			}

			sb.append("\t" + "\t" + "setAdditionalInfo(" + invokerName + ");").append("\n");
			sb.append("\t" + "\t" + "logInvocation(" + invokerName + ");").append("\n");
			sb.append("\t" + "\t");
			if(methodEl.getOutParameterTypes().isEmpty())
			{
				sb.append("checkFault(");
			}
			else
			{
				sb.append("return ");

			}
			sb.append(invokerName + ".invoke(");
			sb.append("\"" + type + "\"").append(", ");

			if(! methodEl.getOutParameterTypes().isEmpty())
				sb.append("arg1");
			else
				sb.append("null");

			if(! methodEl.getOutParameterTypes().isEmpty())
			{
				sb.append(", " + returnTypeClass);
			}
			sb.append(")");

			if(methodEl.getOutParameterTypes().isEmpty())
			{
				sb.append(")");
			}
			sb.append(";").append("\n");
		}

		sb.append("\t" + "}");

		return sb.toString();
	}
}
